/*
 * Copyright 2020 Espressif Systems (Shanghai) Co., Ltd.
 * All rights reserved.
 *
 * You may not use this file except in compliance with the terms and
 * conditions set forth in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS. ESPRESSIF/AMAZON SPECIFICALLY
 * DISCLAIMS, WITH RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS,
 * IMPLIED, OR STATUTORY, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 */
#include <string.h>
#include "FreeRTOS.h"
#include "stdint.h"
#include "driver/gpio.h"

#include "esp_log.h"
#include "esp_err.h"
#include "iot_gpio.h"


#define ESP_INTR_FLAG_DEFAULT 0

static const char *TAG = "esp-hal-gpio";

typedef struct {
    int32_t gpio_num;
    gpio_config_t iot_gpio_conf;
    void (*func)(uint8_t arg1, void *arg2);
    void *arg2;
    uint8_t ucPinState;
    GPIO_FuncTypeDef set_function_val;
} gpio_ctx_t;

static volatile u8 gpio_status_bitmask[5];
static volatile u8 gpio_int_status_bitmask[5];

static inline int findbitposition(GPIO_PinTypeDef gpio) {
	int i;
	int val = gpio & 0x00ff;

	for (i=0; i<8; i++)
		if( val == BIT(i)) {
			return i;
		}
	return 8;
}

#define GPIO_GROUP(x) (x>>8)
#define GPIO_PIN(x)	findbitposition(x)

/* iot_is_valid_gpio - validate if this is a valid pin
 *
 * Return: true if pin is valid, false if not
 */
inline static int iot_is_valid_gpio(GPIO_PinTypeDef gpio)
{
	int group = GPIO_GROUP(gpio);
	int pin = GPIO_PIN(gpio);

	if (group <=4 && pin <=7)
		return TRUE;
	else
		return FALSE;
}

/* is_gpio_open - validate the pin if is open
 *
 * Return: true if pin is open, false if not
 */
inline static int iot_is_open_gpio(GPIO_PinTypeDef gpio)
{
	int group = GPIO_GROUP(gpio);
	int pin = GPIO_PIN(gpio);

	if (gpio_status_bitmask[group] & 1<<pin) {
		return TRUE;
	}
	return FALSE;
}

/* iot_is_open_intr_gpio - validate the pin if is intr open
 *
 * Return: true if pin is open, false if not
 */
inline static int iot_is_open_intr_gpio(GPIO_PinTypeDef gpio)
{
    int group = GPIO_GROUP(gpio);
    int pin = GPIO_PIN(gpio);

    if (gpio_int_status_bitmask[group] & 1<<pin)
        return TRUE;

    return FALSE;
}


IotGpioHandle_t iot_gpio_open( int32_t lGpioNumber )
{
    int group = GPIO_GROUP(lGpioNumber);
    int pin = GPIO_PIN(lGpioNumber);
    if (!iot_is_valid_gpio(lGpioNumber)) {
        ESP_LOGE(TAG, "%s Invalid arguments %04x", __func__, lGpioNumber);
        return NULL;
    }
    if (!(iot_is_open_gpio(lGpioNumber))) {
        gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) calloc(1, sizeof(gpio_ctx_t));
        if (gpio_ctx == NULL) {
            ESP_LOGE(TAG, "Could not allocate memory for gpio context");
            return NULL;
        }
        gpio_ctx->gpio_num = lGpioNumber;
        gpio_ctx->iot_gpio_conf.pin = gpio_ctx->gpio_num;

        gpio_status_bitmask[group] |= BIT(pin);

        IotGpioHandle_t iot_gpio_handler = (void *)gpio_ctx;
        ESP_LOGD(TAG, "%s GPIO Number %d, GPIO bitmask %04x", __func__, lGpioNumber, gpio_status_bitmask[group]);
        return iot_gpio_handler;
    } else {
        ESP_LOGD(TAG, "%s GPIO Number %d, already open", __func__, lGpioNumber);
    }
    return NULL;
}

static void iot_hal_gpio_set_output_mode(IotGpioOutputMode_t *gpio_set_output_mode, gpio_config_t *gpio_conf)
{
    switch (*gpio_set_output_mode) {
    ESP_LOGD(TAG, "gpio output mode: %d", *gpio_set_output_mode);
    case eGpioPushPull :
        gpio_conf->mode = PIN_OUTPUT_ENABLE;
        break;
    default :
        ESP_LOGD(TAG, "gpio output mode %d not supported", *gpio_set_output_mode);
        break;
    }
}

static void iot_hal_gpio_get_output_mode(IotGpioOutputMode_t *gpio_get_output_mode, gpio_config_t *gpio_conf)
{
    switch (gpio_conf->mode) {
    case PIN_OUTPUT_ENABLE :
        *gpio_get_output_mode = eGpioPushPull;
        break;
	/* FIXME: do we support open drain?
    case GPIO_MODE_DEF_OUTPUT :
        *gpio_get_output_mode = eGpioPushPull;
        break;
    */
    default :
        ESP_LOGD(TAG, "gpio output mode %d not supported", gpio_conf->mode);
        break;
    }
}

static void iot_hal_gpio_set_pullup_pulldown(IotGpioPull_t *gpio_set_pullup_pulldown, gpio_config_t *gpio_conf)
{
    switch (*gpio_set_pullup_pulldown) {
    ESP_LOGD(TAG, "gpio pullup/pulldown mode: %d", *gpio_set_pullup_pulldown);
    case eGpioPullNone :
        gpio_conf->pull_down_en = PM_PIN_UP_DOWN_FLOAT;
        gpio_conf->pull_up_en = PM_PIN_UP_DOWN_FLOAT;
        break;
    case eGpioPullUp :
        gpio_conf->pull_down_en = PM_PIN_UP_DOWN_FLOAT;
        gpio_conf->pull_up_en = PM_PIN_PULLUP_10K; /* FIXME: PM_PIN_PULLUP_10K */
        break;
    case eGpioPullDown :
        gpio_conf->pull_down_en = PM_PIN_PULLDOWN_100K;
        gpio_conf->pull_up_en =  PM_PIN_UP_DOWN_FLOAT;
        break;
    default :
        ESP_LOGD(TAG, "gpio set pullup/pulldown not supported");
        break;
    }
}

static void iot_hal_gpio_get_pullup_pulldown(IotGpioPull_t *gpio_get_pullup_pulldown, gpio_config_t *gpio_conf)
{
    if (gpio_conf->pull_down_en == PM_PIN_PULLDOWN_100K) {
        *gpio_get_pullup_pulldown = eGpioPullDown;
    } else if ((gpio_conf->pull_up_en == PM_PIN_PULLUP_1M) ||
    		   (gpio_conf->pull_up_en == PM_PIN_PULLUP_10K)) {
        *gpio_get_pullup_pulldown = eGpioPullUp;
    } else if (gpio_conf->pull_up_en == PM_PIN_UP_DOWN_FLOAT &&
			   gpio_conf->pull_down_en == PM_PIN_UP_DOWN_FLOAT) {
        *gpio_get_pullup_pulldown = eGpioPullNone;
    }
}

static void iot_hal_gpio_set_interrupt(IotGpioInterrupt_t *gpio_set_interrupt, gpio_config_t *gpio_conf)
{
    switch (*gpio_set_interrupt) {
    ESP_LOGD(TAG, "gpio interrupt mode: %d", *gpio_set_interrupt);
    case eGpioInterruptRising :
        gpio_conf->intr_type = POL_RISING;
        break;
    case eGpioInterruptFalling :
        gpio_conf->intr_type = POL_FALLING;
        break;
    default :
        ESP_LOGD(TAG, "gpio set interrupt %d not supported", *gpio_set_interrupt);
        break;
    }
}

static void iot_hal_gpio_get_interrupt(IotGpioInterrupt_t *gpio_get_interrupt, gpio_config_t *gpio_conf)
{
    switch (gpio_conf->intr_type) {
    case POL_RISING :
        *gpio_get_interrupt = eGpioInterruptRising;
        break;
    case POL_FALLING :
        *gpio_get_interrupt = eGpioInterruptFalling;
        break;
    default :
        ESP_LOGD(TAG, "gpio get interrupt %d not supported", *gpio_set_interrupt);
        break;
    }
}

static void iot_hal_gpio_set_direction(IotGpioDirection_t *gpio_set_direction, gpio_config_t *gpio_conf)
{
    switch (*gpio_set_direction) {
    ESP_LOGD(TAG, "gpio direction: %d", *gpio_set_direction);
    case eGpioDirectionInput:
        gpio_conf->mode = PIN_INPUT_ENABLE;
        break;
    case eGpioDirectionOutput:
        gpio_conf->mode = PIN_OUTPUT_ENABLE;
        break;
    default :
        ESP_LOGD(TAG, "gpio set direction not supported");
        break;
    }
}

static void iot_hal_gpio_get_direction(IotGpioDirection_t *gpio_get_direction, gpio_config_t *gpio_conf)
{
    switch (gpio_conf->mode) {
    case PIN_INPUT_ENABLE:
        *gpio_get_direction = eGpioDirectionInput;
        break;
    case PIN_OUTPUT_ENABLE:
        *gpio_get_direction = eGpioDirectionOutput;
        break;
    default :
        ESP_LOGD(TAG, "gpio get direction not supported");
        break;
    }
}

static void iot_hal_gpio_set_drive_strength(unsigned int drive_strength, gpio_config_t *gpio_conf)
{
	gpio_conf->drive_strength = drive_strength;
}

static void iot_hal_gpio_get_drive_strength(unsigned int *drive_strength, gpio_config_t *gpio_conf)
{
	drive_strength = gpio_conf->drive_strength;
}


int32_t iot_gpio_ioctl( IotGpioHandle_t const pxGpio,
                        IotGpioIoctlRequest_t xRequest,
                        void *const pvBuffer )
{
    esp_err_t ret;
    if (pxGpio == NULL || pvBuffer == NULL) {
        ESP_LOGD(TAG, "Invalid  arguments %s", __func__);
        return IOT_GPIO_INVALID_VALUE;
    }
    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) pxGpio;
    //gpio_config_t io_conf = {0};
    switch (xRequest) {
    case eSetGpioFunction: {
        gpio_ctx->set_function_val = *(GPIO_FuncTypeDef *)pvBuffer;
        gpio_set_func(gpio_ctx->gpio_num, gpio_ctx->set_function_val);
        return IOT_GPIO_SUCCESS;
        //return IOT_GPIO_FUNCTION_NOT_SUPPORTED;
        break;
    }
    case eSetGpioDirection: {
        IotGpioDirection_t *set_direction = (IotGpioDirection_t *)pvBuffer;
        iot_hal_gpio_set_direction(set_direction, &gpio_ctx->iot_gpio_conf);
        if (*set_direction == eGpioDirectionInput) {
            gpio_set_output_en(gpio_ctx->gpio_num, 0);
            gpio_set_input_en(gpio_ctx->gpio_num, 1);
        } else if (eGpioDirectionOutput) {
            gpio_set_output_en(gpio_ctx->gpio_num, 1);
            gpio_set_input_en(gpio_ctx->gpio_num, 0);
        }
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eSetGpioPull: {
        IotGpioPull_t *gpio_set_push_pull = (IotGpioPull_t *)pvBuffer;
        iot_hal_gpio_set_pullup_pulldown(gpio_set_push_pull, &gpio_ctx->iot_gpio_conf);
        gpio_setup_up_down_resistor(gpio_ctx->gpio_num, gpio_ctx->iot_gpio_conf.pull_up_en);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eSetGpioOutputMode: {
        /*
         * FIXME: we only support PUSH_PULL mode and it is this mode by default
         */
        IotGpioOutputMode_t *gpio_set_op_mode = (IotGpioOutputMode_t *)pvBuffer;
        iot_hal_gpio_set_output_mode(gpio_set_op_mode, &gpio_ctx->iot_gpio_conf);

        return IOT_GPIO_SUCCESS;
        break;
    }
    case eSetGpioInterrupt: {
        IotGpioInterrupt_t *gpio_set_intr = (IotGpioInterrupt_t *)pvBuffer;
        iot_hal_gpio_set_interrupt(gpio_set_intr, &gpio_ctx->iot_gpio_conf);
        gpio_set_interrupt(gpio_ctx->gpio_num, gpio_ctx->iot_gpio_conf.intr_type);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eSetGpioSpeed: {
        //need to check the existing APIS
        return IOT_GPIO_FUNCTION_NOT_SUPPORTED;
        break;
    }
    case eSetGpioDriveStrength: {
        /* drive strength: (1: strong 0: poor) */
        unsigned int *gpio_drive_strength = (unsigned int *)pvBuffer;
        iot_hal_gpio_set_drive_strength(gpio_drive_strength, &gpio_ctx->iot_gpio_conf);
        gpio_set_data_strength(gpio_ctx->gpio_num, *gpio_drive_strength);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioFunction: {
        *(uint32_t *)pvBuffer = gpio_ctx->set_function_val;
        //FIXME What is this ?
        //return IOT_GPIO_FUNCTION_NOT_SUPPORTED;
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioDirection: {
        IotGpioDirection_t *get_direction = (IotGpioDirection_t *)pvBuffer;
        iot_hal_gpio_get_direction(get_direction, &gpio_ctx->iot_gpio_conf);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioPull: {
        IotGpioPull_t *gpio_get_push_pull = (IotGpioPull_t *)pvBuffer;
        iot_hal_gpio_get_pullup_pulldown(gpio_get_push_pull, &gpio_ctx->iot_gpio_conf);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioOutputType: {
        IotGpioOutputMode_t *gpio_get_op_mode = (IotGpioOutputMode_t *)pvBuffer;
        iot_hal_gpio_get_output_mode(gpio_get_op_mode, &gpio_ctx->iot_gpio_conf);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioInterrupt: {
        IotGpioInterrupt_t *gpio_get_interrupt = (IotGpioInterrupt_t *)pvBuffer;
        iot_hal_gpio_get_interrupt(gpio_get_interrupt, &gpio_ctx->iot_gpio_conf);
        return IOT_GPIO_SUCCESS;
        break;
    }
    case eGetGpioSpeed: {

        return IOT_GPIO_FUNCTION_NOT_SUPPORTED;
        break;
    }
    case eGetGpioDriveStrength: {
        unsigned int *gpio_drive_strength = (unsigned int *)pvBuffer;
        iot_hal_gpio_get_drive_strength(gpio_drive_strength, &gpio_ctx->iot_gpio_conf);
        return IOT_GPIO_SUCCESS;
        break;
    }
    default: {
        return IOT_GPIO_INVALID_VALUE;
        break;
    }
    }
}

/* FIXME: IRAM_ATTR */

void * gpio_ctx_local;

void gpio_cb (void *arg)
{
    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) arg;

    if (!gpio_ctx)
        return;

    if (gpio_ctx->func) {
        gpio_config_t *gpio_config;
        u8 intr_read = gpio_read(gpio_ctx->gpio_num);
        gpio_config = &gpio_ctx->iot_gpio_conf;
        //if (gpio_config->intr_type = POL_RISING && intr_read)
        gpio_ctx->func(gpio_ctx->ucPinState, gpio_ctx->arg2);
    }
}

void iot_gpio_set_callback( IotGpioHandle_t const pxGpio,
                            IotGpioCallback_t xGpioCallback,
                            void *pvUserContext )
{
	int group;
	int pin;

    if (pxGpio == NULL) {
        ESP_LOGD(TAG, "Invalid  arguments %s", __func__);
        gpio_ctx_local = NULL;
        return;
    }

    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) pxGpio;
    gpio_ctx_local = (void *)gpio_ctx;

    group = GPIO_GROUP(gpio_ctx->gpio_num);
    pin = GPIO_PIN(gpio_ctx->gpio_num);

    gpio_int_status_bitmask[group] = gpio_int_status_bitmask[group] | BIT(pin);

    gpio_ctx->func = xGpioCallback;
    gpio_ctx->arg2 = pvUserContext;

    ESP_LOGD(TAG, "%s GPIO interrupt bitmask %x\n\r", __func__, gpio_int_status_bitmask);

}

int32_t iot_gpio_read_sync( IotGpioHandle_t const pxGpio,
                            uint8_t *pucPinState )
{
	uint8_t temp;
    if (pxGpio == NULL) {
        ESP_LOGD(TAG, "Invalid  arguments %s", __func__);
        return IOT_GPIO_INVALID_VALUE;
    }
    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) pxGpio;

    temp = gpio_read(gpio_ctx->gpio_num);

    /* 8258 gpio read is not just high/low, but a value of the pin
     * so temporarily validate if there is a value, means high, otherwise is low
     */
    *pucPinState = (temp) ? 1 : 0;
    return IOT_GPIO_SUCCESS;
}

int32_t iot_gpio_write_sync( IotGpioHandle_t const pxGpio,
                             uint8_t pucPinState )
{
    esp_err_t ret = ESP_OK;
    if (pxGpio == NULL) {
        ESP_LOGD(TAG, "Invalid  arguments %s", __func__);
        return IOT_GPIO_INVALID_VALUE;
    }
    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) pxGpio;
    gpio_write(gpio_ctx->gpio_num, pucPinState);
    return IOT_GPIO_SUCCESS;
}

int32_t iot_gpio_close( IotGpioHandle_t const pxGpio )
{
    if (pxGpio == NULL) {
        ESP_LOGD(TAG, "Invalid  arguments %s", __func__);
        return IOT_GPIO_INVALID_VALUE;
    }
    gpio_ctx_t *gpio_ctx = (gpio_ctx_t *) pxGpio;
    if (iot_is_valid_gpio(gpio_ctx->gpio_num) && iot_is_open_gpio(gpio_ctx->gpio_num)) {
        ESP_LOGD(TAG, "%s GPIO Number %d, GPIO bitmask %d, GPIO interrupt bitmask %x\n\r",
            __func__, gpio_ctx->gpio_num, gpio_status_bitmask, gpio_int_status_bitmask);
        int group = GPIO_GROUP(gpio_ctx->gpio_num);
        int pin = GPIO_PIN(gpio_ctx->gpio_num);
        if (iot_is_open_intr_gpio(gpio_ctx->gpio_num)) {
            gpio_en_interrupt(gpio_ctx->gpio_num, 0);
            gpio_ctx_local = NULL;
            gpio_int_status_bitmask[group] = gpio_int_status_bitmask[group] & ~(BIT(pin));
        }
        gpio_status_bitmask[group] = gpio_status_bitmask[group] & ~(BIT(pin));
        free(pxGpio);
        return IOT_GPIO_SUCCESS;
    } else {
        return IOT_GPIO_INVALID_VALUE;
    }
}
